Dieses Notebook bereitet die Daten für die Intelligent Zoning Engine vor. Es speichert
- entities.geojson — Schulen, deren Geokoordinaten und Attribute: entity_id, capacity, und andere Attribute
- entities.csv — Statistische Blöcke Berlins und optimierungsrelevante Attribute: entity_id, capacity
- units.geojson — Statistische Blöcke Berlins, deren Geometrie und Attribute
- units.csv — Statsitische Blöcke Berlins und optimierungsrelevante Attribute: unit_id, population, percentage_sgb
- weights.csv — optimierungsrelevante Gewichte wie Fußwege / Spalten: entity_id, unit_id, weight, value
- assignment.csv — eine initiale Zuordnung / Spalten: unit_id, entity_id
Bereits in anderen scripten wurde vorbereitet:
- Fußwegen von einer großen Sichprobe von (Wohn-)Gebäuden zu allen Schulen wurden berechnet und in
route_matrix.csv gespeichert
- Die Stichprobe wurde in
sampled_buildings.csv gespeichert
Die Daten werden wiefolgt vorbereitet:
- pro Block werden die Anzahl der einzuschulenden Kinder mit Hilfe der Einwohnerzahlen nach Alter auf LOR-Ebene in
EWR201512E_Matrix.csv hochgerechnet
- Kinder des LOR werden Anteilig nach Einwohnerzahl des Blocks im Verhältnis zum LOR auf die Blöcke verteilt
- es werden minimale, durchschnittliche und maximale Fußwege aus jedem Block errechnet
TODO: - die sozioökonomischen Faktoren werden aus den Wahlbezirken auf die Blöcke hochgerechnet (https://github.com/berlinermorgenpost/cogran)
Laden der Daten
re_schulstand = readOGR('download/re_schulstand.geojson', layer = 'OGRGeoJSON')
OGR data source with driver: GeoJSON
Source: "download/re_schulstand.geojson", layer: "OGRGeoJSON"
with 709 features
It has 20 fields
Schulwege
Schulkapazitäten und Einwohnerzahler auf LOR-Ebene
kapas = read_csv('download/anmeldezahlen.csv') %>% filter(grepl('G', Schulnummer)) %>% filter(!is.na(`Plätze`))
einwohner_lor = read_delim('download/EWR201512E_Matrix.csv', delim=';')
Überprüfung der Vollständigkeit der Daten über Anmeldezahlen/Kapazitäten
re_schulstand_df = re_schulstand %>% as.data.frame() %>% rename(lon=coords.x1, lat=coords.x2)
re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>%
group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n()))
Joining, by = "Bezirk"
Für welche Bezirke haben wir für alle Schulen Kapazitäten gegeben?
re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% mutate(BEZIRK=enc2utf8(as.character(BEZIRK))) %>% group_by(BEZIRK) %>% summarise(`Anzahl Schulen` = n()) %>%
rename(Bezirk=BEZIRK) %>% left_join(kapas %>% group_by(Bezirk) %>% summarise(`Mit Kapazität` = n())) %>% filter(`Anzahl Schulen` == `Mit Kapazität`)
Joining, by = "Bezirk"
Überprüfung ob die Liste der Schulen und Liste der Schulen mit Kapazitätsinformationen gleich sind:
bezirk = 'Tempelhof-Schöneberg'
schulen_mit_kapa = kapas %>% filter(Bezirk == bezirk) %>% .$Schulnummer
schulen_mit_kapa
[1] "07G01" "07G02" "07G03" "07G05" "07G06" "07G07" "07G10" "07G12" "07G13" "07G14" "07G15" "07G16" "07G17" "07G18" "07G19" "07G20" "07G21" "07G22"
[19] "07G23" "07G24" "07G25" "07G26" "07G27" "07G28" "07G29" "07G30" "07G31" "07G32" "07G34" "07G35" "07G36" "07G37"
grundschulen = re_schulstand %>% as.data.frame() %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK == bezirk) %>% .$spatial_name
'In Anmeldeliste, fehlt in Schulstand'
[1] "In Anmeldeliste, fehlt in Schulstand"
setdiff(schulen_mit_kapa, grundschulen)
character(0)
'In re_schulstand, fehlt in Anmeldeliste'
[1] "In re_schulstand, fehlt in Anmeldeliste"
setdiff(grundschulen, schulen_mit_kapa)
character(0)
map = get_map('Berlin')
Map from URL : http://maps.googleapis.com/maps/api/staticmap?center=Berlin&zoom=10&size=640x640&scale=2&maptype=terrain&language=en-EN&sensor=false
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Berlin&sensor=false
re_schulstand_df_w_kapas = re_schulstand_df %>% left_join(kapas, by=c('spatial_name'='Schulnummer'))
joining character vector and factor, coercing into character vector
Plot aller Schulen, mit der Info, ob Kapazitätsinformationen verfügbar sind.
data = re_schulstand_df_w_kapas %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk) %>% mutate(missing.capa=is.na(`Plätze`))
ggmap(map) + geom_point(aes(lon, lat, color=missing.capa), data=data) +
coord_map(xlim=c(min(data$lon)-0.01, max(data$lon)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Filter auf Schulen mit Kapazitätsinformationen (für T-S sind das alle):
relevant_schools = re_schulstand_df %>% filter(grepl('G', spatial_name)) %>% filter(BEZIRK==bezirk & !is.na(`Plätze`)) %>% .$spatial_name
relevant_schools
Mapping Bezirk->LOR->Block
df_bez = as.data.frame(bez)
df_lor = as.data.frame(lor)
df_blk = as.data.frame(blk)
Sanity-Check: LORs und Blöcke im Bezirk
bez_id = filter(df_bez, BEZNAME == bezirk)$BEZ
relevant_lors = df_lor %>% filter(BEZ == bez_id)
relevant_blks = df_blk %>% filter(BEZ == bez_id)
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=lor[lor$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez, color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Blöcke im Bezirk
ggplot() + geom_path(aes(x=long, y=lat, group=group), data=blk[blk$BEZ == bez_id,]) + coord_map() + geom_path(aes(x=long, y=lat, group=group), data=bez[bez$BEZ == bez_id,], color='red')
Regions defined for each Polygons
Regions defined for each Polygons

Kinder im Bezirk auf Blöcke hochrechnen
Über die Einwohnerinformationen in RBS_OD_BLK_2015_12.geojson kann EWR201512E_Matrix.csv von LOR-Ebene auf Blockebene hochgerechnet werden.
TODO: Stattdessen mit https://github.com/berlinermorgenpost/cogran machen?
Plot der 6-Jährigen nach EWR201512E_Matrix.csv
TODO neue Daten von Torres? TODO Mittel über mehrere Jahre verwenden? Prognose?
relevant_ewr = einwohner_lor %>% select(RAUMID, E_E06_07) %>% filter(RAUMID %in% relevant_lors$PLR) %>%
mutate(kids=as.numeric(gsub(',','.',E_E06_07))) %>% as.data.frame()
data = tidy(lor[lor$BEZ == bez_id,], region='PLR') %>% inner_join(relevant_ewr, by=c('id'='RAUMID'))
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Plot der Einwohner auf Blockebene
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(df_blk, by=c('id'='BLK')) %>% mutate(Einw=ifelse(Einw==0, NA, Einw))
joining character vector and factor, coercing into character vector
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=Einw), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Hochrrechnung auf Blöcke
kids_in_blks = relevant_blks %>% group_by(PLR) %>% mutate(EinwRatio = Einw/sum(Einw)) %>% ungroup %>% left_join(relevant_ewr, by=c('PLR'='RAUMID')) %>% mutate(kids = EinwRatio*kids) %>% select(BEZ, PLR, BLK, Einw, kids) %>% as.data.frame()
joining character vector and factor, coercing into character vector
row.names(kids_in_blks) = kids_in_blks$BLK
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(kids_in_blks, by=c('id'='BLK')) %>% mutate(kids=ifelse(kids==0, NA, kids), Einw=ifelse(Einw==0, NA, Einw))
joining character vector and factor, coercing into character vector
0
[1] 0
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=kids), data=data) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

Verfügbare Plätze
Überprüfung der Summe der Kapazitäten, Anmeldungen und Kinderstatistiken
'Summe Kapas'
[1] "Summe Kapas"
relevant_kapas %>% .$Kapa %>% sum
[1] 2584
'Anmeldungen'
[1] "Anmeldungen"
kapas %>% mutate(Anmeldungen = as.numeric(gsub('[^0-9]', '', Anmeldungen))) %>% filter(Schulnummer %in% relevant_schools) %>% .$Anmeldungen %>% sum
[1] 2752
'Kids laut Statistik'
[1] "Kids laut Statistik"
kids_in_blks$kids %>% sum
[1] 2855
relevant_ewr$kids %>% sum
[1] 2855
Schulwege von Blöcken zu Schulen aggregieren
Für jedes Wohngebäude suchen wir den zugehörigen Block
residential_buildings_blocks = sampled_buildings %>% inner_join(df_blk) %>% filter(BEZ == bez_id)
Joining, by = "BLK"
residential_buildings_blocks
routes_from_blks = residential_buildings_blocks %>%
left_join(route_matrix %>% filter(dst %in% relevant_schools), by=c('OI'='src'))
joining character vector and factor, coercing into character vector
head(routes_from_blks)
Plot der relevanten Blöcke (mit Wohngebäuden)
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% inner_join(routes_from_blks %>% group_by(BLK) %>% summarise(n=n()), by=c('id'='BLK'))
joining character vector and factor, coercing into character vector
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group), fill='red', data=data) +
#geom_point(aes(x=lon, y=lat), data=rb_df, color='black', size=0.01) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_from_blks = routes_from_blks %>% as.data.frame() %>% group_by(BLK, dst) %>% summarise(min=min(distance), avg=mean(distance), med=median(distance), max=max(distance)) %>% ungroup
travel_from_blks
Plot der Blöcke mit Färbung nach durchschnittlichem Weg zur nächsten Schule
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(travel_from_blks %>% group_by(BLK) %>% top_n(1, -avg), by=c('id'='BLK'))
joining factor and character vector, coercing into character vector
ggmap(map) + geom_polygon(aes(x=long, y=lat, group=group, fill=-avg), data=data) +
geom_point(aes(lon, lat), color='red', data = re_schulstand_df %>% filter(BEZIRK==bezirk & SCHULART=='Grundschule')) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01))

travel_matrix = travel_from_blks %>% select(BLK, dst, avg) %>% spread(dst, avg)
dim(travel_matrix)
[1] 1012 33
travel_matrix
Select relevant data
optim_kapas = relevant_kapas
optim_kids_in_blks = kids_in_blks %>% filter(kids > 0) %>% inner_join(travel_matrix, by='BLK') %>% select(BLK, kids) %>% mutate(kids=kids)
nrow(optim_kids_in_blks)
[1] 1012
nrow(optim_kapas)
[1] 32
select_schools = as.character(optim_kapas$Schulnummer)
select_blks = as.character(optim_kids_in_blks$BLK)
optim_matrix = inner_join(optim_kids_in_blks, travel_matrix, by='BLK')[select_schools]
dim(optim_matrix)
[1] 1012 32
optim_kapas$Kapa %>% sum
[1] 2584
optim_kids_in_blks$kids %>% sum
[1] 2844.886
Naive Zuordnung: Jeder Block zur nächsten Schule
solution = optim_matrix %>% mutate(BLK=optim_kids_in_blks$BLK) %>% gather(school, dist, -BLK) %>% group_by(BLK) %>% top_n(1, -dist) %>% ungroup
optim_matrix %>% t %>% as.data.frame %>% summarise_each(funs(min)) %>% sum()
[1] 786223.1
solines = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school')) %>% inner_join(cbind(as.data.frame(coordinates(blk[blk$BEZ == bez_id,])), blk[blk$BEZ == bez_id,]@data['BLK']))
joining factor and character vector, coercing into character vectorJoining, by = "BLK"
data = tidy(blk[blk$BEZ == bez_id,], region='BLK') %>% left_join(solution, by=c('id'='BLK'))
joining factor and character vector, coercing into character vector
ggmap(map, darken = c(0.5, 'white')) + geom_polygon(aes(x=long, y=lat, group=group, fill=school), data=data) +
geom_segment(aes(x=V1,y=V2,xend=lon,yend=lat), data=solines, size=0.3) +
geom_point(aes(lon, lat), color='black', size=2, data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
geom_point(aes(lon, lat, color=spatial_name), data = re_schulstand_df %>% inner_join(solution, by=c('spatial_name'='school'))) +
coord_map(xlim=c(min(data$long)-0.01, max(data$long)+0.01), ylim=c(min(data$lat)-0.01, max(data$lat)+0.01)) +
guides(color=F, fill=F)
joining factor and character vector, coercing into character vectorjoining factor and character vector, coercing into character vector

Darstellung der Zuordnung als Tabelle
library(formattable)
solution %>% inner_join(optim_kids_in_blks, by='BLK') %>% inner_join(travel_from_blks, by=c('BLK'='BLK', 'school'='dst')) %>%
group_by(school) %>% summarise(
kids=sum(kids),
num_blocks=n(),
min_dist=min(min),
avg_dist=mean((kids*avg)/sum(kids)),
max_dist=max(max)
) %>%
inner_join(relevant_kapas, by=c('school'='Schulnummer')) %>%
mutate(
utilization=kids/Kapa
) %>% select(
Schule=school,
`Blöcke`=num_blocks,
Kapazität=Kapa,
Kinder=kids,
Auslastung=utilization,
`Weg (min)`=min_dist,
`Weg (Ø)`=avg_dist,
`Weg (max)`=max_dist
) %>%
formattable(
list(
Kinder = formatter("span", x ~ digits(x, 2)),
Auslastung = formatter("span",
style = x ~ style(color = ifelse(x < 1, "green", "red")),
x ~ icontext(ifelse(x < 1, "ok", "remove"), percent(x))
),
`Weg (Ø)` = proportion_bar("lightblue"),
`Weg (min)` = proportion_bar("lightblue"),
`Weg (max)` = proportion_bar("lightblue")
)
)
Daten für die App speichern
- entities.geojson
- entities.csv
- units.geojson
- units.csv
- weights.csv
- assignment.csv
Alte Daten
write_rds(solution, 'app/data/init_solution.rds', 'gz')
write_rds(subset(blk, BEZ == bez_id), 'app/data/blocks.rds', 'gz')
write_rds(subset(re_schulstand_df, spatial_name %in% relevant_schools), 'app/data/schools.rds', 'gz')
write_rds(subset(bez, BEZ == bez_id), 'app/data/bez.rds', 'gz')
block_stats = optim_kids_in_blks %>% inner_join(travel_from_blks, by=c('BLK'='BLK')) %>% inner_join(relevant_kapas, by=c('dst'='Schulnummer')) %>% inner_join(re_schulstand_df, by=c('dst'='spatial_name'))
joining character vector and factor, coercing into character vector
write_rds(block_stats, 'app/data/block_stats.rds', 'gz')
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgbGlicywgaW5jbHVkZT1GLCB3YXJuaW5nPUZ9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ21hcCkKbGlicmFyeShwdXJycikKbGlicmFyeShrbml0cikKbGlicmFyeShicm9vbSkKbGlicmFyeShtYXB0b29scykKYGBgCgpEaWVzZXMgTm90ZWJvb2sgYmVyZWl0ZXQgZGllIERhdGVuIGbDvHIgZGllIEludGVsbGlnZW50IFpvbmluZyBFbmdpbmUgdm9yLiBFcyBzcGVpY2hlcnQKCi0gZW50aXRpZXMuZ2VvanNvbiDigJQgU2NodWxlbiwgZGVyZW4gR2Vva29vcmRpbmF0ZW4gdW5kIEF0dHJpYnV0ZTogZW50aXR5X2lkLCBjYXBhY2l0eSwgdW5kIGFuZGVyZSBBdHRyaWJ1dGUKLSBlbnRpdGllcy5jc3Yg4oCUIFN0YXRpc3Rpc2NoZSBCbMO2Y2tlIEJlcmxpbnMgdW5kIG9wdGltaWVydW5nc3JlbGV2YW50ZSBBdHRyaWJ1dGU6IGVudGl0eV9pZCwgY2FwYWNpdHkKLSB1bml0cy5nZW9qc29uIOKAlCBTdGF0aXN0aXNjaGUgQmzDtmNrZSBCZXJsaW5zLCBkZXJlbiBHZW9tZXRyaWUgdW5kIEF0dHJpYnV0ZQotIHVuaXRzLmNzdiDigJQgU3RhdHNpdGlzY2hlIEJsw7Zja2UgQmVybGlucyB1bmQgb3B0aW1pZXJ1bmdzcmVsZXZhbnRlIEF0dHJpYnV0ZTogdW5pdF9pZCwgcG9wdWxhdGlvbiwgcGVyY2VudGFnZV9zZ2IKLSB3ZWlnaHRzLmNzdiDigJQgb3B0aW1pZXJ1bmdzcmVsZXZhbnRlIEdld2ljaHRlIHdpZSBGdcOfd2VnZSAvIFNwYWx0ZW46IGVudGl0eV9pZCwgdW5pdF9pZCwgd2VpZ2h0LCB2YWx1ZQotIGFzc2lnbm1lbnQuY3N2IOKAlCBlaW5lIGluaXRpYWxlIFp1b3JkbnVuZyAvIFNwYWx0ZW46IHVuaXRfaWQsIGVudGl0eV9pZAoKQmVyZWl0cyBpbiBhbmRlcmVuIHNjcmlwdGVuIHd1cmRlIHZvcmJlcmVpdGV0OgoKLSBGdcOfd2VnZW4gdm9uIGVpbmVyIGdyb8OfZW4gU2ljaHByb2JlIHZvbiAoX1dvaG5fLSlHZWLDpHVkZW4genUgYWxsZW4gU2NodWxlbiB3dXJkZW4gYmVyZWNobmV0IHVuZCBpbiBgcm91dGVfbWF0cml4LmNzdmAgZ2VzcGVpY2hlcnQKLSBEaWUgU3RpY2hwcm9iZSB3dXJkZSBpbiBgc2FtcGxlZF9idWlsZGluZ3MuY3N2YCBnZXNwZWljaGVydAoKRGllIERhdGVuIHdlcmRlbiB3aWVmb2xndCB2b3JiZXJlaXRldDoKCi0gcHJvIEJsb2NrIHdlcmRlbiBkaWUgQW56YWhsIGRlciBlaW56dXNjaHVsZW5kZW4gS2luZGVyIG1pdCBIaWxmZSBkZXIgRWlud29obmVyemFobGVuIG5hY2ggQWx0ZXIgYXVmIExPUi1FYmVuZSBpbiBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YCBob2NoZ2VyZWNobmV0CiAgICAtIEtpbmRlciBkZXMgTE9SIHdlcmRlbiBBbnRlaWxpZyBuYWNoIEVpbndvaG5lcnphaGwgZGVzIEJsb2NrcyBpbSBWZXJow6RsdG5pcyB6dW0gTE9SIGF1ZiBkaWUgQmzDtmNrZSB2ZXJ0ZWlsdAotIGVzIHdlcmRlbiBtaW5pbWFsZSwgZHVyY2hzY2huaXR0bGljaGUgdW5kIG1heGltYWxlIEZ1w593ZWdlIGF1cyBqZWRlbSBCbG9jayBlcnJlY2huZXQKClRPRE86Ci0gZGllIHNvemlvw7Zrb25vbWlzY2hlbiBGYWt0b3JlbiB3ZXJkZW4gYXVzIGRlbiBXYWhsYmV6aXJrZW4gYXVmIGRpZSBCbMO2Y2tlIGhvY2hnZXJlY2huZXQgKGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJsaW5lcm1vcmdlbnBvc3QvY29ncmFuKQoKIyMgTGFkZW4gZGVyIERhdGVuCgpgYGB7ciBsb2FkIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnNhbXBsZWRfYnVpbGRpbmdzID0gcmVhZF9yZHMoJ291dHB1dC9zYW1wbGVkX2J1aWxkaW5ncy5yZHMnKQpiZXogPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkVaXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nKQpibGsgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfQkxLXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nKQpsb3IgPSByZWFkT0dSKCdkb3dubG9hZC9SQlNfT0RfTE9SXzIwMTVfMTIuZ2VvanNvbicsIGxheWVyID0gJ09HUkdlb0pTT04nKQpyZV9zY2h1bHN0YW5kID0gcmVhZE9HUignZG93bmxvYWQvcmVfc2NodWxzdGFuZC5nZW9qc29uJywgbGF5ZXIgPSAnT0dSR2VvSlNPTicpCmBgYAoKIyMjIFNjaHVsd2VnZQoKYGBge3J9CnJvdXRlX21hdHJpeCA9IHJlYWRfcmRzKCdvdXRwdXQvcm91dGVfbWF0cml4LnJkcycpCmBgYAoKIyMjIFNjaHVsa2FwYXppdMOkdGVuIHVuZCBFaW53b2huZXJ6YWhsZXIgYXVmIExPUi1FYmVuZQoKYGBge3J9CmthcGFzID0gcmVhZF9jc3YoJ2Rvd25sb2FkL2FubWVsZGV6YWhsZW4uY3N2JykgJT4lIGZpbHRlcihncmVwbCgnRycsIFNjaHVsbnVtbWVyKSkgJT4lIGZpbHRlcighaXMubmEoYFBsw6R0emVgKSkKZWlud29obmVyX2xvciA9IHJlYWRfZGVsaW0oJ2Rvd25sb2FkL0VXUjIwMTUxMkVfTWF0cml4LmNzdicsIGRlbGltPSc7JykKYGBgCgojIyDDnGJlcnByw7xmdW5nIGRlciBWb2xsc3TDpG5kaWdrZWl0IGRlciBEYXRlbiDDvGJlciBBbm1lbGRlemFobGVuL0thcGF6aXTDpHRlbgoKYGBge3J9CnJlX3NjaHVsc3RhbmRfZGYgPSByZV9zY2h1bHN0YW5kICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHJlbmFtZShsb249Y29vcmRzLngxLCBsYXQ9Y29vcmRzLngyKQpyZV9zY2h1bHN0YW5kX2RmICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgbXV0YXRlKEJFWklSSz1lbmMydXRmOChhcy5jaGFyYWN0ZXIoQkVaSVJLKSkpICU+JQogIGdyb3VwX2J5KEJFWklSSykgJT4lIHN1bW1hcmlzZShgQW56YWhsIFNjaHVsZW5gID0gbigpKSAlPiUKICByZW5hbWUoQmV6aXJrPUJFWklSSykgJT4lIGxlZnRfam9pbihrYXBhcyAlPiUgZ3JvdXBfYnkoQmV6aXJrKSAlPiUgc3VtbWFyaXNlKGBNaXQgS2FwYXppdMOkdGAgPSBuKCkpKQpgYGAKCkbDvHIgd2VsY2hlIEJlemlya2UgaGFiZW4gd2lyIGbDvHIgYWxsZSBTY2h1bGVuIEthcGF6aXTDpHRlbiBnZWdlYmVuPwoKYGBge3J9CnJlX3NjaHVsc3RhbmQgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZmlsdGVyKGdyZXBsKCdHJywgc3BhdGlhbF9uYW1lKSkgJT4lIG11dGF0ZShCRVpJUks9ZW5jMnV0ZjgoYXMuY2hhcmFjdGVyKEJFWklSSykpKSAlPiUgZ3JvdXBfYnkoQkVaSVJLKSAlPiUgc3VtbWFyaXNlKGBBbnphaGwgU2NodWxlbmAgPSBuKCkpICU+JQogIHJlbmFtZShCZXppcms9QkVaSVJLKSAlPiUgbGVmdF9qb2luKGthcGFzICU+JSBncm91cF9ieShCZXppcmspICU+JSBzdW1tYXJpc2UoYE1pdCBLYXBheml0w6R0YCA9IG4oKSkpICU+JSBmaWx0ZXIoYEFuemFobCBTY2h1bGVuYCA9PSBgTWl0IEthcGF6aXTDpHRgKQpgYGAKCsOcYmVycHLDvGZ1bmcgb2IgZGllIExpc3RlIGRlciBTY2h1bGVuIHVuZCBMaXN0ZSBkZXIgU2NodWxlbiBtaXQgS2FwYXppdMOkdHNpbmZvcm1hdGlvbmVuIGdsZWljaCBzaW5kOgoKYGBge3J9CmJlemlyayA9ICdUZW1wZWxob2YtU2Now7ZuZWJlcmcnCnNjaHVsZW5fbWl0X2thcGEgPSBrYXBhcyAlPiUgZmlsdGVyKEJlemlyayA9PSBiZXppcmspICU+JSAuJFNjaHVsbnVtbWVyCnNjaHVsZW5fbWl0X2thcGEKZ3J1bmRzY2h1bGVuID0gcmVfc2NodWxzdGFuZCAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBmaWx0ZXIoZ3JlcGwoJ0cnLCBzcGF0aWFsX25hbWUpKSAlPiUgZmlsdGVyKEJFWklSSyA9PSBiZXppcmspICU+JSAuJHNwYXRpYWxfbmFtZQonSW4gQW5tZWxkZWxpc3RlLCBmZWhsdCBpbiBTY2h1bHN0YW5kJwpzZXRkaWZmKHNjaHVsZW5fbWl0X2thcGEsIGdydW5kc2NodWxlbikKJ0luIHJlX3NjaHVsc3RhbmQsIGZlaGx0IGluIEFubWVsZGVsaXN0ZScKc2V0ZGlmZihncnVuZHNjaHVsZW4sIHNjaHVsZW5fbWl0X2thcGEpCmBgYAoKCmBgYHtyfQptYXAgPSBnZXRfbWFwKCdCZXJsaW4nKQpgYGAKCgpgYGB7cn0KcmVfc2NodWxzdGFuZF9kZl93X2thcGFzID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgbGVmdF9qb2luKGthcGFzLCBieT1jKCdzcGF0aWFsX25hbWUnPSdTY2h1bG51bW1lcicpKQpgYGAKClBsb3QgYWxsZXIgU2NodWxlbiwgbWl0IGRlciBJbmZvLCBvYiBLYXBheml0w6R0c2luZm9ybWF0aW9uZW4gdmVyZsO8Z2JhciBzaW5kLgoKYGBge3J9CmRhdGEgPSByZV9zY2h1bHN0YW5kX2RmX3dfa2FwYXMgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmspICU+JSBtdXRhdGUobWlzc2luZy5jYXBhPWlzLm5hKGBQbMOkdHplYCkpCmdnbWFwKG1hcCkgKyBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCwgY29sb3I9bWlzc2luZy5jYXBhKSwgZGF0YT1kYXRhKSArCiAgICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbiktMC4wMSwgbWF4KGRhdGEkbG9uKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgpGaWx0ZXIgYXVmIFNjaHVsZW4gbWl0IEthcGF6aXTDpHRzaW5mb3JtYXRpb25lbiAoZsO8ciBULVMgc2luZCBkYXMgYWxsZSk6CgpgYGB7cn0KcmVsZXZhbnRfc2Nob29scyA9IHJlX3NjaHVsc3RhbmRfZGYgJT4lIGZpbHRlcihncmVwbCgnRycsIHNwYXRpYWxfbmFtZSkpICU+JSBmaWx0ZXIoQkVaSVJLPT1iZXppcmsgJiAhaXMubmEoYFBsw6R0emVgKSkgJT4lIC4kc3BhdGlhbF9uYW1lCnJlbGV2YW50X3NjaG9vbHMKYGBgCgojIyBNYXBwaW5nIEJlemlyay0+TE9SLT5CbG9jawoKYGBge3J9CmRmX2JleiA9IGFzLmRhdGEuZnJhbWUoYmV6KQpkZl9sb3IgPSBhcy5kYXRhLmZyYW1lKGxvcikKZGZfYmxrID0gYXMuZGF0YS5mcmFtZShibGspCmBgYAoKIyMjIFNhbml0eS1DaGVjazogTE9ScyB1bmQgQmzDtmNrZSBpbSBCZXppcmsKCmBgYHtyfQpiZXpfaWQgPSBmaWx0ZXIoZGZfYmV6LCBCRVpOQU1FID09IGJlemlyaykkQkVaCnJlbGV2YW50X2xvcnMgPSBkZl9sb3IgJT4lIGZpbHRlcihCRVogPT0gYmV6X2lkKQpyZWxldmFudF9ibGtzID0gZGZfYmxrICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKYGBgCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1sb3JbbG9yJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleiwgY29sb3I9J3JlZCcpCmBgYAoKIyMjIEJsw7Zja2UgaW0gQmV6aXJrCgpgYGB7cn0KZ2dwbG90KCkgKyBnZW9tX3BhdGgoYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwKSwgZGF0YT1ibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkgKyBjb29yZF9tYXAoKSArIGdlb21fcGF0aChhZXMoeD1sb25nLCB5PWxhdCwgZ3JvdXA9Z3JvdXApLCBkYXRhPWJleltiZXokQkVaID09IGJlel9pZCxdLCBjb2xvcj0ncmVkJykKYGBgCgojIyBLaW5kZXIgaW0gQmV6aXJrIGF1ZiBCbMO2Y2tlIGhvY2hyZWNobmVuCgrDnGJlciBkaWUgRWlud29obmVyaW5mb3JtYXRpb25lbiBpbiBgUkJTX09EX0JMS18yMDE1XzEyLmdlb2pzb25gIGthbm4gYEVXUjIwMTUxMkVfTWF0cml4LmNzdmAgdm9uIExPUi1FYmVuZSBhdWYgQmxvY2tlYmVuZSBob2NoZ2VyZWNobmV0IHdlcmRlbi4KClRPRE86IFN0YXR0ZGVzc2VuIG1pdCBodHRwczovL2dpdGh1Yi5jb20vYmVybGluZXJtb3JnZW5wb3N0L2NvZ3JhbiBtYWNoZW4/CgojIyMgUGxvdCBkZXIgNi1Kw6RocmlnZW4gbmFjaCBgRVdSMjAxNTEyRV9NYXRyaXguY3N2YAoKVE9ETyBuZXVlIERhdGVuIHZvbiBUb3JyZXM/ClRPRE8gTWl0dGVsIMO8YmVyIG1laHJlcmUgSmFocmUgdmVyd2VuZGVuPyBQcm9nbm9zZT8KCmBgYHtyfQpyZWxldmFudF9ld3IgPSBlaW53b2huZXJfbG9yICU+JSBzZWxlY3QoUkFVTUlELCBFX0UwNl8wNykgJT4lIGZpbHRlcihSQVVNSUQgJWluJSByZWxldmFudF9sb3JzJFBMUikgJT4lCiAgbXV0YXRlKGtpZHM9YXMubnVtZXJpYyhnc3ViKCcsJywnLicsRV9FMDZfMDcpKSkgJT4lIGFzLmRhdGEuZnJhbWUoKQoKZGF0YSA9IHRpZHkobG9yW2xvciRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nUExSJykgJT4lIGlubmVyX2pvaW4ocmVsZXZhbnRfZXdyLCBieT1jKCdpZCc9J1JBVU1JRCcpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1raWRzKSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgojIyMgUGxvdCBkZXIgRWlud29obmVyIGF1ZiBCbG9ja2ViZW5lCgpgYGB7cn0KZGF0YSA9IHRpZHkoYmxrW2JsayRCRVogPT0gYmV6X2lkLF0sIHJlZ2lvbj0nQkxLJykgJT4lIGlubmVyX2pvaW4oZGZfYmxrLCBieT1jKCdpZCc9J0JMSycpKSAlPiUgbXV0YXRlKEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKMApnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCwgZmlsbD1FaW53KSwgZGF0YT1kYXRhKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKIyMjIEhvY2hycmVjaG51bmcgYXVmIEJsw7Zja2UKCmBgYHtyfQpraWRzX2luX2Jsa3MgPSByZWxldmFudF9ibGtzICU+JSBncm91cF9ieShQTFIpICU+JSBtdXRhdGUoRWlud1JhdGlvID0gRWludy9zdW0oRWludykpICU+JSB1bmdyb3VwICU+JSBsZWZ0X2pvaW4ocmVsZXZhbnRfZXdyLCBieT1jKCdQTFInPSdSQVVNSUQnKSkgJT4lIG11dGF0ZShraWRzID0gRWlud1JhdGlvKmtpZHMpICU+JSBzZWxlY3QoQkVaLCBQTFIsIEJMSywgRWludywga2lkcykgJT4lIGFzLmRhdGEuZnJhbWUoKQpyb3cubmFtZXMoa2lkc19pbl9ibGtzKSA9IGtpZHNfaW5fYmxrcyRCTEsKCmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBpbm5lcl9qb2luKGtpZHNfaW5fYmxrcywgYnk9YygnaWQnPSdCTEsnKSkgJT4lIG11dGF0ZShraWRzPWlmZWxzZShraWRzPT0wLCBOQSwga2lkcyksIEVpbnc9aWZlbHNlKEVpbnc9PTAsIE5BLCBFaW53KSkKCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPWtpZHMpLCBkYXRhPWRhdGEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCiMjIFZlcmbDvGdiYXJlIFBsw6R0emUKCmBgYHtyfQpyZWxldmFudF9rYXBhcyA9IGthcGFzICU+JSBzZWxlY3QoU2NodWxudW1tZXIsIEthcGE9YFBsw6R0emVgKSAlPiUgZmlsdGVyKFNjaHVsbnVtbWVyICVpbiUgcmVsZXZhbnRfc2Nob29scykgJT4lIGFzLmRhdGEuZnJhbWUoKQojcm93Lm5hbWVzKHJlbGV2YW50X2thcGFzKSA9IHJlbGV2YW50X2thcGFzJFNjaHVsbnVtbWVyCmBgYAoKIyMjIMOcYmVycHLDvGZ1bmcgZGVyIFN1bW1lIGRlciBLYXBheml0w6R0ZW4sIEFubWVsZHVuZ2VuIHVuZCBLaW5kZXJzdGF0aXN0aWtlbgoKYGBge3J9CidTdW1tZSBLYXBhcycKcmVsZXZhbnRfa2FwYXMgJT4lIC4kS2FwYSAlPiUgc3VtCidBbm1lbGR1bmdlbicKa2FwYXMgJT4lIG11dGF0ZShBbm1lbGR1bmdlbiA9IGFzLm51bWVyaWMoZ3N1YignW14wLTldJywgJycsIEFubWVsZHVuZ2VuKSkpICU+JSBmaWx0ZXIoU2NodWxudW1tZXIgJWluJSByZWxldmFudF9zY2hvb2xzKSAlPiUgLiRBbm1lbGR1bmdlbiAlPiUgc3VtCidLaWRzIGxhdXQgU3RhdGlzdGlrJwpraWRzX2luX2Jsa3Mka2lkcyAlPiUgc3VtCnJlbGV2YW50X2V3ciRraWRzICU+JSBzdW0KYGBgCgojIyBTY2h1bHdlZ2Ugdm9uIEJsw7Zja2VuIHp1IFNjaHVsZW4gYWdncmVnaWVyZW4KCkbDvHIgamVkZXMgV29obmdlYsOkdWRlIHN1Y2hlbiB3aXIgZGVuIHp1Z2Vow7ZyaWdlbiBCbG9jawoKYGBge3J9CnJlc2lkZW50aWFsX2J1aWxkaW5nc19ibG9ja3MgPSBzYW1wbGVkX2J1aWxkaW5ncyAlPiUgaW5uZXJfam9pbihkZl9ibGspICU+JSBmaWx0ZXIoQkVaID09IGJlel9pZCkKcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcwpgYGAKCmBgYHtyfQpyb3V0ZXNfZnJvbV9ibGtzID0gcmVzaWRlbnRpYWxfYnVpbGRpbmdzX2Jsb2NrcyAlPiUKICBsZWZ0X2pvaW4ocm91dGVfbWF0cml4ICU+JSBmaWx0ZXIoZHN0ICVpbiUgcmVsZXZhbnRfc2Nob29scyksIGJ5PWMoJ09JJz0nc3JjJykpCmhlYWQocm91dGVzX2Zyb21fYmxrcykKYGBgCgojIyMgUGxvdCBkZXIgcmVsZXZhbnRlbiBCbMO2Y2tlIChtaXQgV29obmdlYsOkdWRlbikKCmBgYHtyfQpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgaW5uZXJfam9pbihyb3V0ZXNfZnJvbV9ibGtzICU+JSBncm91cF9ieShCTEspICU+JSBzdW1tYXJpc2Uobj1uKCkpLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXApICsgZ2VvbV9wb2x5Z29uKGFlcyh4PWxvbmcsIHk9bGF0LCBncm91cD1ncm91cCksIGZpbGw9J3JlZCcsIGRhdGE9ZGF0YSkgKwogICNnZW9tX3BvaW50KGFlcyh4PWxvbiwgeT1sYXQpLCBkYXRhPXJiX2RmLCBjb2xvcj0nYmxhY2snLCBzaXplPTAuMDEpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKQpgYGAKCgpgYGB7cn0KdHJhdmVsX2Zyb21fYmxrcyA9IHJvdXRlc19mcm9tX2Jsa3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZ3JvdXBfYnkoQkxLLCBkc3QpICU+JSBzdW1tYXJpc2UobWluPW1pbihkaXN0YW5jZSksIGF2Zz1tZWFuKGRpc3RhbmNlKSwgbWVkPW1lZGlhbihkaXN0YW5jZSksIG1heD1tYXgoZGlzdGFuY2UpKSAlPiUgdW5ncm91cAp0cmF2ZWxfZnJvbV9ibGtzCmBgYAoKIyMjIFBsb3QgZGVyIEJsw7Zja2UgbWl0IEbDpHJidW5nIG5hY2ggZHVyY2hzY2huaXR0bGljaGVtIFdlZyB6dXIgbsOkY2hzdGVuIFNjaHVsZQoKYGBge3J9CmRhdGEgPSB0aWR5KGJsa1tibGskQkVaID09IGJlel9pZCxdLCByZWdpb249J0JMSycpICU+JSBsZWZ0X2pvaW4odHJhdmVsX2Zyb21fYmxrcyAlPiUgZ3JvdXBfYnkoQkxLKSAlPiUgdG9wX24oMSwgLWF2ZyksIGJ5PWMoJ2lkJz0nQkxLJykpCmdnbWFwKG1hcCkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPS1hdmcpLCBkYXRhPWRhdGEpICsKICBnZW9tX3BvaW50KGFlcyhsb24sIGxhdCksIGNvbG9yPSdyZWQnLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgZmlsdGVyKEJFWklSSz09YmV6aXJrICYgU0NIVUxBUlQ9PSdHcnVuZHNjaHVsZScpKSArCiAgY29vcmRfbWFwKHhsaW09YyhtaW4oZGF0YSRsb25nKS0wLjAxLCBtYXgoZGF0YSRsb25nKSswLjAxKSwgeWxpbT1jKG1pbihkYXRhJGxhdCktMC4wMSwgbWF4KGRhdGEkbGF0KSswLjAxKSkKYGBgCgoKYGBge3J9CnRyYXZlbF9tYXRyaXggPSB0cmF2ZWxfZnJvbV9ibGtzICU+JSBzZWxlY3QoQkxLLCBkc3QsIGF2ZykgJT4lIHNwcmVhZChkc3QsIGF2ZykKZGltKHRyYXZlbF9tYXRyaXgpCnRyYXZlbF9tYXRyaXgKYGBgCgojIFNlbGVjdCByZWxldmFudCBkYXRhCgpgYGB7cn0Kb3B0aW1fa2FwYXMgPSByZWxldmFudF9rYXBhcwpvcHRpbV9raWRzX2luX2Jsa3MgPSBraWRzX2luX2Jsa3MgJT4lIGZpbHRlcihraWRzID4gMCkgJT4lIGlubmVyX2pvaW4odHJhdmVsX21hdHJpeCwgYnk9J0JMSycpICU+JSBzZWxlY3QoQkxLLCBraWRzKSAlPiUgbXV0YXRlKGtpZHM9a2lkcykKbnJvdyhvcHRpbV9raWRzX2luX2Jsa3MpCm5yb3cob3B0aW1fa2FwYXMpCgpzZWxlY3Rfc2Nob29scyA9IGFzLmNoYXJhY3RlcihvcHRpbV9rYXBhcyRTY2h1bG51bW1lcikKc2VsZWN0X2Jsa3MgPSBhcy5jaGFyYWN0ZXIob3B0aW1fa2lkc19pbl9ibGtzJEJMSykKCm9wdGltX21hdHJpeCA9IGlubmVyX2pvaW4ob3B0aW1fa2lkc19pbl9ibGtzLCB0cmF2ZWxfbWF0cml4LCBieT0nQkxLJylbc2VsZWN0X3NjaG9vbHNdCgpkaW0ob3B0aW1fbWF0cml4KQoKb3B0aW1fa2FwYXMkS2FwYSAlPiUgc3VtCm9wdGltX2tpZHNfaW5fYmxrcyRraWRzICU+JSBzdW0KYGBgCgojIyBOYWl2ZSBadW9yZG51bmc6IEplZGVyIEJsb2NrIHp1ciBuw6RjaHN0ZW4gU2NodWxlCgpgYGB7cn0Kc29sdXRpb24gPSBvcHRpbV9tYXRyaXggJT4lIG11dGF0ZShCTEs9b3B0aW1fa2lkc19pbl9ibGtzJEJMSykgJT4lIGdhdGhlcihzY2hvb2wsIGRpc3QsIC1CTEspICU+JSBncm91cF9ieShCTEspICU+JSB0b3BfbigxLCAtZGlzdCkgJT4lIHVuZ3JvdXAKCm9wdGltX21hdHJpeCAlPiUgdCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc3VtbWFyaXNlX2VhY2goZnVucyhtaW4pKSAlPiUgc3VtKCkKCnNvbGluZXMgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkgJT4lIGlubmVyX2pvaW4oY2JpbmQoYXMuZGF0YS5mcmFtZShjb29yZGluYXRlcyhibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSkpLCBibGtbYmxrJEJFWiA9PSBiZXpfaWQsXUBkYXRhWydCTEsnXSkpCgpkYXRhID0gdGlkeShibGtbYmxrJEJFWiA9PSBiZXpfaWQsXSwgcmVnaW9uPSdCTEsnKSAlPiUgbGVmdF9qb2luKHNvbHV0aW9uLCBieT1jKCdpZCc9J0JMSycpKQpnZ21hcChtYXAsIGRhcmtlbiA9IGMoMC41LCAnd2hpdGUnKSkgKyBnZW9tX3BvbHlnb24oYWVzKHg9bG9uZywgeT1sYXQsIGdyb3VwPWdyb3VwLCBmaWxsPXNjaG9vbCksIGRhdGE9ZGF0YSkgKwogIGdlb21fc2VnbWVudChhZXMoeD1WMSx5PVYyLHhlbmQ9bG9uLHllbmQ9bGF0KSwgZGF0YT1zb2xpbmVzLCBzaXplPTAuMykgKwogIGdlb21fcG9pbnQoYWVzKGxvbiwgbGF0KSwgY29sb3I9J2JsYWNrJywgc2l6ZT0yLCBkYXRhID0gcmVfc2NodWxzdGFuZF9kZiAlPiUgaW5uZXJfam9pbihzb2x1dGlvbiwgYnk9Yygnc3BhdGlhbF9uYW1lJz0nc2Nob29sJykpKSArCiAgZ2VvbV9wb2ludChhZXMobG9uLCBsYXQsIGNvbG9yPXNwYXRpYWxfbmFtZSksIGRhdGEgPSByZV9zY2h1bHN0YW5kX2RmICU+JSBpbm5lcl9qb2luKHNvbHV0aW9uLCBieT1jKCdzcGF0aWFsX25hbWUnPSdzY2hvb2wnKSkpICsKICBjb29yZF9tYXAoeGxpbT1jKG1pbihkYXRhJGxvbmcpLTAuMDEsIG1heChkYXRhJGxvbmcpKzAuMDEpLCB5bGltPWMobWluKGRhdGEkbGF0KS0wLjAxLCBtYXgoZGF0YSRsYXQpKzAuMDEpKSArCiAgZ3VpZGVzKGNvbG9yPUYsIGZpbGw9RikKYGBgCgojIyBEYXJzdGVsbHVuZyBkZXIgWnVvcmRudW5nIGFscyBUYWJlbGxlCgpgYGB7cn0KbGlicmFyeShmb3JtYXR0YWJsZSkKYGBgCgpgYGB7cn0Kc29sdXRpb24gJT4lIGlubmVyX2pvaW4ob3B0aW1fa2lkc19pbl9ibGtzLCBieT0nQkxLJykgJT4lIGlubmVyX2pvaW4odHJhdmVsX2Zyb21fYmxrcywgYnk9YygnQkxLJz0nQkxLJywgJ3NjaG9vbCc9J2RzdCcpKSAlPiUKICBncm91cF9ieShzY2hvb2wpICU+JSBzdW1tYXJpc2UoCiAgICBraWRzPXN1bShraWRzKSwKICAgIG51bV9ibG9ja3M9bigpLAogICAgbWluX2Rpc3Q9bWluKG1pbiksCiAgICBhdmdfZGlzdD1tZWFuKChraWRzKmF2Zykvc3VtKGtpZHMpKSwKICAgIG1heF9kaXN0PW1heChtYXgpCiAgKSAlPiUKICBpbm5lcl9qb2luKHJlbGV2YW50X2thcGFzLCBieT1jKCdzY2hvb2wnPSdTY2h1bG51bW1lcicpKSAlPiUKICBtdXRhdGUoCiAgICB1dGlsaXphdGlvbj1raWRzL0thcGEKICApICU+JSBzZWxlY3QoCiAgIFNjaHVsZT1zY2hvb2wsCiAgIGBCbMO2Y2tlYD1udW1fYmxvY2tzLAogICBLYXBheml0w6R0PUthcGEsCiAgIEtpbmRlcj1raWRzLAogICBBdXNsYXN0dW5nPXV0aWxpemF0aW9uLAogICBgV2VnIChtaW4pYD1taW5fZGlzdCwKICAgYFdlZyAow5gpYD1hdmdfZGlzdCwKICAgYFdlZyAobWF4KWA9bWF4X2Rpc3QKICApICU+JQogIGZvcm1hdHRhYmxlKAogICAgbGlzdCgKICAgICAgS2luZGVyID0gZm9ybWF0dGVyKCJzcGFuIiwgeCB+IGRpZ2l0cyh4LCAyKSksCiAgICAgIEF1c2xhc3R1bmcgPSBmb3JtYXR0ZXIoInNwYW4iLAogICAgICAgIHN0eWxlID0geCB+IHN0eWxlKGNvbG9yID0gaWZlbHNlKHggPCAxLCAiZ3JlZW4iLCAicmVkIikpLAogICAgICAgIHggfiBpY29udGV4dChpZmVsc2UoeCA8IDEsICJvayIsICJyZW1vdmUiKSwgcGVyY2VudCh4KSkKICAgICAgKSwKICAgICAgYFdlZyAow5gpYCA9IHByb3BvcnRpb25fYmFyKCJsaWdodGJsdWUiKSwKICAgICAgYFdlZyAobWluKWAgPSBwcm9wb3J0aW9uX2JhcigibGlnaHRibHVlIiksCiAgICAgIGBXZWcgKG1heClgID0gcHJvcG9ydGlvbl9iYXIoImxpZ2h0Ymx1ZSIpCiAgICApCiAgKQpgYGAKCgojIyBEYXRlbiBmw7xyIGRpZSBBcHAgc3BlaWNoZXJuCgotIGVudGl0aWVzLmdlb2pzb24KLSBlbnRpdGllcy5jc3YKLSB1bml0cy5nZW9qc29uCi0gdW5pdHMuY3N2Ci0gd2VpZ2h0cy5jc3YKLSBhc3NpZ25tZW50LmNzdgoKIyMjIE5ldWUgRGF0ZW4KClRPRE8KCiMjIyBBbHRlIERhdGVuCgpgYGB7cn0Kd3JpdGVfcmRzKHNvbHV0aW9uLCAnYXBwL2RhdGEvaW5pdF9zb2x1dGlvbi5yZHMnLCAnZ3onKQp3cml0ZV9yZHMoc3Vic2V0KGJsaywgQkVaID09IGJlel9pZCksICdhcHAvZGF0YS9ibG9ja3MucmRzJywgJ2d6JykKd3JpdGVfcmRzKHN1YnNldChyZV9zY2h1bHN0YW5kX2RmLCBzcGF0aWFsX25hbWUgJWluJSByZWxldmFudF9zY2hvb2xzKSwgJ2FwcC9kYXRhL3NjaG9vbHMucmRzJywgJ2d6JykKd3JpdGVfcmRzKHN1YnNldChiZXosIEJFWiA9PSBiZXpfaWQpLCAnYXBwL2RhdGEvYmV6LnJkcycsICdneicpCgpibG9ja19zdGF0cyA9IG9wdGltX2tpZHNfaW5fYmxrcyAlPiUgaW5uZXJfam9pbih0cmF2ZWxfZnJvbV9ibGtzLCBieT1jKCdCTEsnPSdCTEsnKSkgJT4lIGlubmVyX2pvaW4ocmVsZXZhbnRfa2FwYXMsIGJ5PWMoJ2RzdCc9J1NjaHVsbnVtbWVyJykpICU+JSBpbm5lcl9qb2luKHJlX3NjaHVsc3RhbmRfZGYsIGJ5PWMoJ2RzdCc9J3NwYXRpYWxfbmFtZScpKQp3cml0ZV9yZHMoYmxvY2tfc3RhdHMsICdhcHAvZGF0YS9ibG9ja19zdGF0cy5yZHMnLCAnZ3onKQpgYGAKCg==